
#include "GuidedNativeImageIO.h"
#include "ImageCoordinateGeometry.h"
#include "AmaranthusCommon.h"

#include <QMessageBox>
#include <QStringList>
#include <QDir>
#include <QString>
#include <QErrorMessage>

#include "itkImageIOBase.h"
#include "itkImageSeriesReader.h"
#include "itkJPEGImageIO.h"
//#include "itkAnalyzeImageIO.h"
#include "itkGiplImageIO.h"
#include "itkMetaImageIO.h"
#include "itkNrrdImageIO.h"
#include "itkGDCMImageIO.h"
#include "itkGE4ImageIO.h"
#include "itkGE5ImageIO.h"
#include "itkNiftiImageIO.h"
#include "itkSiemensVisionImageIO.h"
#include "itkVTKImageIO.h"
#include "itkImage.h"
#include "itkImageIOFactory.h"
#include "itkGDCMSeriesFileNames.h"
#include "itkImportImageContainer.h"
#include "itkImportImageFilter.h"

#include "itkBlobSpatialObject.h"
#include "itkLineIterator.h"
#include "itkImageFileWriter.h"

#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

// Number of file formats should be equal to enum number!

GuidedNativeImageIO::GuidedNativeImageIO()
{
    m_FileFormat = FORMAT_COUNT;
}

GuidedNativeImageIO::~GuidedNativeImageIO()
{
    DeallocateImage();
}

GuidedNativeImageIO::FileFormat
GuidedNativeImageIO::DetermineFileFormatFromFile(const QStringList &fileNames, const QDir &directory)
{
    m_FileNames = fileNames;
    m_FileFormat = FORMAT_COUNT;

    for (int i = 0; i < m_FileNames.size(); i++)
    {
        if(fileNames[i].contains("dcm", Qt::CaseInsensitive))
            m_FileFormat = FORMAT_DICOM;
        else
            m_FileFormat = FORMAT_COUNT;
    }
    if (m_FileFormat == FORMAT_DICOM)
        return m_FileFormat;

    for (int j = 0; j < m_FileNames.size(); j++)
    {
        std::ifstream infile;
        infile.open(directory.filePath(fileNames[j]).toStdString().c_str());
        std::vector<char> buffer(4);

        infile.seekg(0);
        infile.read(&buffer[0], buffer.size());
        std::string act(buffer.begin(), buffer.end());

        infile.seekg(128);
        infile.read( &buffer[0], buffer.size() );
        std::string dicm( buffer.begin(), buffer.end() );

        if (act == "ACT1")
        {
            m_FileFormat = FORMAT_ACT;
        }

        else if (dicm == "DICM")
        {
            if(m_FileFormat!=FORMAT_ACT)
                m_FileFormat = FORMAT_DICOM;
        }
    }

    return m_FileFormat;
}

const QStringList&
GuidedNativeImageIO::GetDICOMSeriesUID(const QDir &directory)
{
    m_DICOMNamesGenerator = DICOMNamesGeneratorType::New();

    try
    {
        m_DICOMNamesGenerator->SetDirectory(directory.absolutePath().toStdString());
    }
    catch(...)
    {
        (new QErrorMessage())->showMessage("Error listing DICOM files in the directory: \n"+directory.dirName());
    }

    if(m_DICOMNamesGenerator->GetSeriesUIDs().size() == 0)
        (new QErrorMessage())->showMessage("No DICOM series found in the directory: \n"+directory.dirName());
    else
    {
        std::vector<std::string> listUids = m_DICOMNamesGenerator->GetSeriesUIDs();
        for (std::vector<std::string>::iterator iterator = listUids.begin(); iterator < listUids.end(); iterator++)
        {
            m_DICOMUids << QString::fromStdString(*iterator);
        }
    }
    return m_DICOMUids;
}

QStringList GuidedNativeImageIO::GetACTSeriesUID(const QDir &directory)
{
    std::ifstream currentFile;
    std::vector<char> bufferProc(2);
    QStringList UID;

    for (int i = 0; i < m_FileNames.size(); i++)
    {
        QString path = m_FileNames[i];

        currentFile.open(path.toStdString().c_str(), std::ios::in);
        currentFile.seekg(13);
        currentFile.read(&bufferProc[0], bufferProc.size());
        std::string uid(bufferProc.begin(), bufferProc.end());

        bool repeated = false;
        for(int j = 0; j < UID.size(); j++)
        {
            if (UID[j] == QString::fromStdString(uid))
                repeated = true;
        }        

        if (repeated == false)
            UID.push_back(QString::fromStdString(uid));
    }

    return UID;
}

void
GuidedNativeImageIO::ReadNativeImage(QString uid)
{
    if(m_FileFormat == FORMAT_DICOM)
    {
        m_ImageIOBase = itk::GDCMImageIO::New();
        m_DICOMFiles = m_DICOMNamesGenerator->GetFileNames(uid.toStdString());

        if(m_DICOMFiles.size() == 0)
            (new QErrorMessage())->showMessage("No DICOM files found in the DICOM directory");

        m_ImageIOBase->SetFileName(m_DICOMFiles[0]);
        m_ImageIOBase->ReadImageInformation();

        m_ImageProperties = new ImageProperties;
        m_ImageProperties->uid = uid;

        switch(m_ImageIOBase->GetComponentType())
        {
        case itk::ImageIOBase::UCHAR:  DoReadDICOM<unsigned char>();  break;
        case itk::ImageIOBase::CHAR:   DoReadDICOM<signed char>();    break;
        case itk::ImageIOBase::USHORT: DoReadDICOM<unsigned short>(); break;
        case itk::ImageIOBase::SHORT:  DoReadDICOM<signed short>();   break;
        case itk::ImageIOBase::UINT:   DoReadDICOM<unsigned int>();   break;
        case itk::ImageIOBase::INT:    DoReadDICOM<signed int>();     break;
        case itk::ImageIOBase::ULONG:  DoReadDICOM<unsigned long>();  break;
        case itk::ImageIOBase::LONG:   DoReadDICOM<signed long>();    break;
        case itk::ImageIOBase::FLOAT:  DoReadDICOM<float>();          break;
        case itk::ImageIOBase::DOUBLE: DoReadDICOM<double>();         break;
        default:
            (new QErrorMessage())->showMessage("Unknown Pixel Type when reading DICOM image");
        }        
    }

    else if (m_FileFormat == FORMAT_ACT)
    {
        std::ifstream currentFile;
        int rows, columns;
        char byteOrder;

        QString path = m_FileNames[0];
        currentFile.open(path.toStdString().c_str(), std::ios::in);

        currentFile.seekg(27);
        currentFile >> rows;

        currentFile.seekg(32);
        currentFile >> columns;

        currentFile.seekg(37);
        currentFile >> byteOrder;

        currentFile.close();

        m_ImageProperties = new ImageProperties;
        m_ImageProperties->uid = uid;
        m_ImageProperties->dimensions.append(rows);
        m_ImageProperties->dimensions.append(columns);
        m_ImageProperties->dimensions.append(m_FileNames.size());

        if (QString(byteOrder) == "0")
        {
            m_ImageProperties->endian = "Big Endian";
            m_ImageProperties->pixelTypeString = "unsigned short";
            m_ImageProperties->pixelType = ImageIOBase::USHORT;
        }
        else if (QString(byteOrder) == "1")
        {
            m_ImageProperties->endian = "Little Endian";
            m_ImageProperties->pixelTypeString = "unsigned short";
            m_ImageProperties->pixelType = ImageIOBase::USHORT;
        }
        else if (QString(byteOrder) == "2")
        {
            m_ImageProperties->endian = "Big Endian";
            m_ImageProperties->pixelTypeString = "signed short";
            m_ImageProperties->pixelType = ImageIOBase::SHORT;
        }
        else if (QString(byteOrder) == "3")
        {
            m_ImageProperties->endian = "Little Endian";
            m_ImageProperties->pixelTypeString = "signed short";
            m_ImageProperties->pixelType = ImageIOBase::SHORT;
        }

        switch(m_ImageProperties->pixelType)
        {
        case itk::ImageIOBase::USHORT:   DoReadACT<unsigned short>();   break;
        case itk::ImageIOBase::SHORT:    DoReadACT<signed short>();     break;
        default:
            (new QErrorMessage())->showMessage("Unknown Pixel Type when reading ACT image");
        }       
    }
     this->ReadImageProperties();
}

void
GuidedNativeImageIO::ReadACTROIFile(QString file)
{
    GreyImageType::Pointer maskImage = GreyImageType::New();

  /*  GreyImageType::SizeType regionSize;
    regionSize.SetElement(0, m_ImageProperties->dimensions[0]);
    regionSize.SetElement(1, m_ImageProperties->dimensions[1]);
    regionSize.SetElement(2, m_ImageProperties->dimensions[2]);

    GreyImageType::IndexType regionIndex;
    regionIndex.Fill(0);

    GreyImageType::RegionType region;
    region.SetSize(regionSize);
    region.SetIndex(regionIndex);

    GreyImageType::PixelType pixel;
    pixel = 0;

    maskImage->SetRegions(region);
    maskImage->Allocate();
    maskImage->FillBuffer(pixel);*/

 /*   std::ifstream currentFile;
    std::string line, line2, namefile;
    unsigned int slice = m_ImageProperties->dimensions[0]*m_ImageProperties->dimensions[1];
    unsigned int volume = m_ImageProperties->dimensions[0]*m_ImageProperties->dimensions[1]*m_ImageProperties->dimensions[2];
    int pos, numPoints;

    int *getdata = new int[volume];

    for (int i = 0; i < m_FileNames.size(); i++)
    {
        QFileInfo onlyName(m_FileNames[i]);
        namefile = onlyName.fileName().toStdString();
        currentFile.open(file.toStdString().c_str(), std::ios::in);
        while (!currentFile.eof())
        {
            getline (currentFile,line);
            pos = line.find(namefile);

            if (pos != std::string::npos)
            {
                for(int j = 0; j < 3; ++j)
                    getline (currentFile,line);
                std::stringstream lineStream(line);
                lineStream >> numPoints;
                getline (currentFile,line);

                double *xCoord = new double[numPoints];
                double *yCoord = new double[numPoints];
                double zCoord = i*m_ImageProperties->spacing[2];

                for (int k = 0; k < numPoints; k++)
                {
                    std::stringstream points(line);
                    points >> xCoord[k];
                    points >> yCoord[k];
                    getline (currentFile,line);
                }

                for (int k = 1; k <= numPoints; k++)
                {
                    GreyImageType::IndexType corner1;
                    GreyImageType::IndexType corner2;

                    GreyImageType::PixelType pixel;
                    pixel = 1;

                    corner1[0] = xCoord[k-1];
                    corner1[1] = yCoord[k-1];
                    corner1[2] = zCoord;

                    corner2[0] = xCoord[k];
                    corner2[1] = yCoord[k];
                    corner2[2] = zCoord;

                    itk::LineIterator<GreyImageType> it1(maskImage, corner1, corner2);
                    it1.GoToBegin();
                    while (!it1.IsAtEnd())
                    {
                        it1.Set(pixel);
                        ++it1;
                    }
                }
            }
        }
        currentFile.close();
    }*/
    m_ImageBase = maskImage;
}

template<class TScalar>
void
GuidedNativeImageIO::DoReadACT()
{
    typedef itk::Image<TScalar, 3> NativeImageType;
    typedef typename NativeImageType::Pointer NativeImagePointer;
    NativeImagePointer image = NativeImageType::New();

    std::ifstream currentFile;
    int shift, thickness, viewport;
    char longitudinal, transverse;

    unsigned int slice = m_ImageProperties->dimensions[0]*m_ImageProperties->dimensions[1];
    unsigned int volume = m_ImageProperties->dimensions[0]*m_ImageProperties->dimensions[1]*m_ImageProperties->dimensions[2];

    TScalar *getdata = new TScalar[volume];

    this->SortACTFileNames();

    for (int i = 0; i < m_FileNames.size(); i++)
    {
        QString path = m_FileNames[i];
        currentFile.open(path.toStdString().c_str(), std::ios::in);

        currentFile.seekg(22);
        currentFile >> shift;

        currentFile.seekg(80);
        currentFile >> longitudinal;

        currentFile.seekg(86);
        currentFile >> transverse;

        currentFile.seekg(87);
        currentFile >> viewport;

        currentFile.seekg(95);
        currentFile >> thickness;

        currentFile.close();

        TScalar* buffer = new TScalar[slice];
        FILE * pFile = fopen(path.toStdString().c_str(), "r");
        fseek (pFile , shift, SEEK_SET );
        size_t result = fread (buffer, sizeof(TScalar), slice, pFile);
        for (int count = 0; count < slice; count++)
        {
            getdata[count+i*slice] = buffer[count];
        }

        fclose(pFile);
    }

    m_ImageProperties->spacing[0] = (double)(viewport/(m_ImageProperties->dimensions[0]*10.0));
    m_ImageProperties->spacing[1] = (double)(viewport/(m_ImageProperties->dimensions[1]*10.0));
    m_ImageProperties->spacing[2] = (double)(thickness/10.0);

    typedef itk::ImportImageFilter<TScalar, 3> ImportFilterType;
    typedef typename ImportFilterType::Pointer ImportFilterPointer;
    ImportFilterPointer importFilter = ImportFilterType::New();

    typename ImportFilterType::SizeType  size;

    size[0]  = m_ImageProperties->dimensions[0];  // size along X
    size[1]  = m_ImageProperties->dimensions[1];  // size along Y
    size[2]  = m_ImageProperties->dimensions[2];  // size along Z

    typename ImportFilterType::IndexType start;
    start.Fill(0);

    typename ImportFilterType::RegionType region;
    region.SetIndex(start);
    region.SetSize(size);

    importFilter->SetRegion(region);

    double origin[3];
    if ((QString(longitudinal) == "H")&&(QString(transverse) == "S"))
    {
        origin[0] = 0.0;
        origin[1] = 0.0;
        origin[2] = m_ImageProperties->dimensions[2];

      //  origin[0] = -122.5;
      //  origin[1] = -132.8;
      //  origin[2] = -50.75;

    }
    else if ((QString(longitudinal) == "H")&&(QString(transverse) == "P"))
    {
        origin[0] = m_ImageProperties->dimensions[0];
        origin[1] = m_ImageProperties->dimensions[1];
        origin[2] = m_ImageProperties->dimensions[2];
    }
    else
    {
        origin[0] = 0;
        origin[1] = 0;
        origin[2] = 0;
    }
    m_ImageProperties->imagePosition.append(origin[0]);
    m_ImageProperties->imagePosition.append(origin[1]);
    m_ImageProperties->imagePosition.append(origin[2]);
    importFilter->SetOrigin(origin);

    double spacing[3];
    spacing[0] = m_ImageProperties->spacing[0];    // along X direction
    spacing[1] = m_ImageProperties->spacing[1];    // along Y direction
    spacing[2] = m_ImageProperties->spacing[2];    // along Z direction

    importFilter->SetSpacing(spacing);
    importFilter->SetImportPointer(getdata, volume, false);

    image = importFilter->GetOutput();
    importFilter->Update();
    m_ImageBase = image;
}

void
GuidedNativeImageIO::SortACTFileNames()
{
    QStringList fileNames;
    std::ifstream currentFile;
    std::vector<char> bufferProc(2);
    int shift;
    int j = 0;

    for (int i = 0; i < m_FileNames.size(); i++)
    {
        QString path = m_FileNames[i];

        currentFile.open(path.toStdString().c_str(), std::ios::in);
        currentFile.seekg(13);
        currentFile.read(&bufferProc[0], bufferProc.size());
        std::string currentUID(bufferProc.begin(), bufferProc.end());
        currentFile.close();

        if(m_ImageProperties->uid == QString::fromStdString(currentUID))
        {
            fileNames.append(m_FileNames[i]);
            j = j+1;
        }
    }

    m_FileNames.clear();
    m_FileNames = fileNames;
    fileNames.clear();

  /*  QMap<int, QString> map;
    for (int i = 0; i < m_FileNames.size(); i++)
    {
        currentFile.open(m_FileNames[i].toStdString().c_str(), std::ios::in);
        currentFile.seekg(82);
        currentFile >> shift;
        currentFile.close();

        map.insert(shift, m_FileNames[i]);
    }

    fileNames = map.values();

    m_FileNames.clear();
    m_FileNames = fileNames;
    fileNames.clear();*/
}

template<class TScalar>
void
GuidedNativeImageIO::DoReadDICOM()
{
    typedef itk::Image<TScalar, 3> NativeImageType;
    typedef typename NativeImageType::Pointer NativeImagePointer;

    NativeImagePointer image = NativeImageType::New();

    if(m_FileFormat == FORMAT_DICOM && m_DICOMFiles.size() > 1)
    {
        typedef itk::ImageSeriesReader<NativeImageType> ReaderType;
        typename ReaderType::Pointer reader = ReaderType::New();

        reader->SetFileNames(m_DICOMFiles);
        reader->SetImageIO(m_ImageIOBase);
        reader->Update();

        image = reader->GetOutput();

        const typename ReaderType::DictionaryArrayType *darr = reader->GetMetaDataDictionaryArray();
        if(darr->size() > 0)
        {
            image->SetMetaDataDictionary(*((*darr)[0]));
        }
    }

    m_ImageBase = image;

    QMessageBox msgBox;
    msgBox.setText("Spacing is... ");
    msgBox.exec();
}

void
GuidedNativeImageIO::ReadImageProperties()
{
    if(m_FileFormat == FORMAT_DICOM)
    {
        m_ImageProperties = new ImageProperties;
        QStringList endianTypes;
        endianTypes << "Big Endian" << "Little Endian" << "Order Not Applicable";
        bool SpacingIsReadfromDICOM = false;

        for(unsigned int i = 0; i < 3; i++)
        {
            m_ImageProperties->dimensions.append(m_ImageBase->GetBufferedRegion().GetSize()[i]);
          //  m_ImageProperties->spacing.append(m_ImageBase->GetSpacing()[i]);
        }

        itk::MetaDataDictionary mdd = m_ImageBase->GetMetaDataDictionary();
        QMap <QString, QString> DICOMLabels;

        std::string value;
        std::string dcm_label;
        for(itk::MetaDataDictionary::ConstIterator itMeta = mdd.Begin(); itMeta != mdd.End(); ++itMeta)
        {
            std::string key = itMeta->first;
            if(itk::ExposeMetaData<std::string>(mdd, key, value))
            {
               /* QMessageBox msgBox;
                msgBox.setText("key is... "+QString::fromStdString(key));
                msgBox.exec();

                QMessageBox msgBox2;
                msgBox2.setText("value is... "+QString::fromStdString(value));
                msgBox2.exec();*/


                if (key.compare("0028|0030")==0)
                {
                   if(SpacingIsReadfromDICOM)
                       this->SplittoQVector(m_ImageProperties->spacing, value);
                   else
                   {
                       QVector<double> spacing(2);
                       this->SplittoQVector(spacing, value);
                       m_ImageProperties->spacing[0] = spacing[0];
                       m_ImageProperties->spacing[1] = spacing[1];
                   }
                }

                if (key.compare("0018|0050")==0)
                {                    
                    m_ImageProperties->spacing[2] = QString::fromStdString(value).toDouble();
                    SpacingIsReadfromDICOM = true;
                }

                if (key.compare("0020|0037")==0)
                {
                    int j = 0;
                    QVector <double> orientation(6);
                    this->SplittoQVector(orientation, value);
                    QVectorIterator<double> i(orientation);
                    while (i.hasNext())
                    {
                        if (j<=2)
                            m_ImageProperties->imageOrientationVectors[0]<<i.next();
                        else
                            m_ImageProperties->imageOrientationVectors[1]<<i.next();
                        j++;
                    }
                }

                if (key.compare("0020|0032")==0)
                {
                    QVector<double> imagePosition(3);
                    this->SplittoQVector(imagePosition, value);
                    m_ImageProperties->imagePosition = imagePosition;
                }

                if (key.compare("0028|1052")==0)
                    m_ImageProperties->rescaleIntercept = QString::fromStdString(value).toDouble();

                if (key.compare("0028|1053")==0)
                    m_ImageProperties->rescaleSlope = QString::fromStdString(value).toDouble();

                if(itk::GDCMImageIO::GetLabelFromTag(key, dcm_label))
                {
                    key = dcm_label;
                    DICOMLabels[QString::fromStdString(key)] = QString::fromStdString(value);
                }
                m_ImageProperties->labels = DICOMLabels;
                m_ImageProperties->sizeInKBytes = m_ImageIOBase->GetImageSizeInBytes()/(1024.0);

                if(m_ImageIOBase->GetComponentType()!= itk::ImageIOBase::UNKNOWNCOMPONENTTYPE)
                {
                    m_ImageProperties->pixelTypeString = QString::fromStdString(m_ImageIOBase->GetComponentTypeAsString(
                                                                                         m_ImageIOBase->GetComponentType()));
                    m_ImageProperties->pixelType = m_ImageIOBase->GetComponentType();
                }
                m_ImageProperties->endian = endianTypes[(unsigned int)(m_ImageIOBase->GetByteOrder() - ImageIOBase::BigEndian)];
            }
        }
    }
    else if (m_FileFormat == FORMAT_ACT)
    {
        std::ifstream currentFile;
        QString path = m_FileNames[0];        
        std::vector<char> bufferMod(2), bufferPatient(4);

        currentFile.open(path.toStdString().c_str(), std::ios::in);
        currentFile.seekg(4);
        currentFile.read(&bufferMod[0], bufferMod.size());
        std::string strmod(bufferMod.begin(), bufferMod.end());
        m_ImageProperties->modality = QString::fromStdString(strmod);

        currentFile.seekg(8);
        currentFile.read(&bufferPatient[0], bufferPatient.size());
        std::string strpatient(bufferPatient.begin(), bufferPatient.end());
        m_ImageProperties->patientNumber = QString::fromStdString(strpatient);

        char dataBase;
        currentFile.seekg(7);
        currentFile >> dataBase;
        m_ImageProperties->dataBase = QString(dataBase);

        QVector<double> orientation[2];
        orientation[0] << 1;
        orientation[0] << 0;
        orientation[0] << 0;
        orientation[1] << 0;
        orientation[1] << 1;
        orientation[1] << 0;

        m_ImageProperties->imageOrientationVectors[0] = orientation[0];
        m_ImageProperties->imageOrientationVectors[1] = orientation[1];
    }

    m_ImageCoordinateGeometry = new ImageCoordinateGeometry(*m_ImageProperties);
    QString lps = m_ImageCoordinateGeometry->GetLPSCode();
    if(m_ImageCoordinateGeometry->IsOblique())
        m_ImageProperties->lps =  "Oblique (closest to "+lps+" )";
    else
        m_ImageProperties->lps = lps;
}

void
GuidedNativeImageIO::SplittoQVector(QVector<double>& field, const std::string value)
{
    const std::string delimiter = "\\";
    std::string::size_type start = 0;
    std::string::size_type finish = 0;
    QVector<double>::Iterator it = field.begin();

    do
    {
        finish = value.find(delimiter, start);
       // double imSp = atof(value.substr(start, finish-start).c_str());
        double imSp = QString(value.substr(start, finish-start).c_str()).toDouble();
        (*it) = imSp;
        it++;
        start = finish + delimiter.size();
    }
    while (finish != std::string::npos);
}


